package com.yy.zhbootgateway.config;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

/**
 * 描述: GateWay 路由配置类
 * <p>
 * 错误：spring cloud gateway启动报错：org.springframework.cloud.gateway.config.GatewayAutoConfiguration
 * spring cloud gateway是基于webflux的
 * <p>
 * 解决问题 extends SpringCloudCircuitBreakerFilterFactory
 * 错误：Method routeLocator in com.yy.zhbootgateway.config.RouteConfiguration required a bean of type 'org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerFilterFactory' that could not be found.
 *
 * @author: yanglin
 * @Date: 2021-11-05-14:30
 * @Version: 1.0
 */
@Slf4j
@Configuration
public class RouteConfiguration {

    /**
     * Spring Cloud Gateway配置CircuitBreaker 一般会用到如下2种GatewayFilter Factory：
     * Hystrix GatewayFilter Factory
     * Spring Cloud CircuitBreaker GatewayFilter Factory
     *
     * 使用resilience4j 熔断器
     * Spring Cloud CircuitBreaker GatewayFilter Factory
     * @return
     */
    @Bean
    public ReactiveResilience4JCircuitBreakerFactory defaultCustomizer() {
        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
                // 滑动窗口的类型为时间窗口
                .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED)
                // 时间窗口的大小为60秒
                .slidingWindowSize(60)
                // 在单位时间窗口内最少需要5次调用才能开始进行统计计算
                .minimumNumberOfCalls(5)
                // 在单位时间窗口内调用失败率达到50%后会启动断路器
                .failureRateThreshold(50)
                // 允许断路器自动由打开状态转换为半开状态
                .enableAutomaticTransitionFromOpenToHalfOpen()
                // 在半开状态下允许进行正常调用的次数
                .permittedNumberOfCallsInHalfOpenState(5)
                // 断路器打开状态转换为半开状态需要等待60秒
                .waitDurationInOpenState(Duration.ofSeconds(60))
                // 所有异常都当作失败来处理
                .recordExceptions(Throwable.class)
                .build();

        ReactiveResilience4JCircuitBreakerFactory factory = new ReactiveResilience4JCircuitBreakerFactory();
        factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
                .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(500)).build())
                .circuitBreakerConfig(circuitBreakerConfig).build());

        return factory;
    }

    /**
     * 熔断器
     * Hystrix GatewayFilter Factory
     * @return
     */
    @Bean
    public HystrixCircuitBreakerFactory defaultConfig() {
        HystrixCircuitBreakerFactory circuitBreakerFactory = new HystrixCircuitBreakerFactory();
        circuitBreakerFactory.configureDefault(id -> HystrixCommand.Setter
                .withGroupKey(HystrixCommandGroupKey.Factory.asKey(id)).andCommandPropertiesDefaults(
                        HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(4000)));

        return circuitBreakerFactory;
    }

    /**
     * 在spring cloud gateway中使用RouteLocator的Bean进行路由转发,等价于在yml配置文件中的配置
     *
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                // 配置路由
                .route("busuness_route",
                        r -> r.path("/business/**")
                                .filters(f -> f.circuitBreaker(
                                        // 定义断路器
                                        c -> c.setName("myCircuitBreaker")
                                                // 当断路器回退被调用时
                                                .setFallbackUri("forward:/inCaseOfFailureUseThis")
                                )
                                        // 跳转？
                                        .rewritePath("/consumingServiceEndpoint", "/backingServiceEndpoint"))
                                // Spring Cloud LoadBalancer负载平衡(由目标URI上的lb前缀定义)
                                // 服务提供者路径
                                .uri("lb://backing-service:8088")
                )
                // 登录页面
                .route("zh-login-01", r -> r.path("/login")
                        .uri("lb://zh-boot-auth"))
                // 注销
                .route("zh-logout-01", r -> r.path("/logout")
                        .uri("lb://zh-boot-auth"))
                // 登录接口
                .route("zh-login-02", r -> r.path("/access/auth/login")
                        .uri("lb://zh-boot-auth"))
                // 登录页面静态资源
                .route("zh-login-03", r -> r.path("/js/**")
                        .uri("lb://zh-auth"))
                .route("zh-login-04", r -> r.path("/css/**")
                        .uri("lb://zh-auth"))
                .route("zh-login-05", r -> r.path("/img/**")
                        .uri("lb://zh-auth"))
                // 主页
                .route("zh-index-01", r -> r.path("/index")
                        .uri("lb://zh-user-ui"))
                // 主页页面静态资源
                .route("zh-index-02", r -> r.path("/static/**")
                        .uri("lb://zh-user-ui"))
                .route("zh-index-03", r -> r.path("/layui/**")
                        .uri("lb://zh-user-ui"))
                .route("zh-index-04", r -> r.path("/common/**")
                        .uri("lb://zh-user-ui"))
                // 用户管理
                .route("zh-user-01", r -> r.path("/user*")
                        .uri("lb://zh-user-ui"))
                .route("zh-user-03", r -> r.path("/user/c/**")
                        .uri("lb://zh-user-ui"))
                // 角色管理
                .route("zh-role-01", r -> r.path("/role*")
                        .uri("lb://zh-user-ui"))
                .route("zh-role-02", r -> r.path("/role/c/**")
                        .uri("lb://zh-user-ui"))
                // 菜单管理
                .route("zh-menu-01", r -> r.path("/permission*")
                        .uri("lb://zh-user-ui"))
                .route("zh-menu-02", r -> r.path("/permission/c/**")
                        .uri("lb://zh-user-ui"))
                // 数据字典
                .route("zh-code-01", r -> r.path("/code*")
                        .uri("lb://zh-user-ui"))
                .route("zh-code-02", r -> r.path("/code/c/**")
                        .uri("lb://zh-user-ui"))
                // 用户等级
                .route("zh-level-01", r -> r.path("/level*")
                        .uri("lb://zh-user-ui"))
                .route("zh-level-02", r -> r.path("/level/c/**")
                        .uri("lb://zh-user-ui"))
                .build();
    }

}
